using System;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.IO;
using System.Collections;
using System.Reflection;
using System.Runtime.CompilerServices;

using ICSharpCode.SharpZipLib.Zip.Compression.Streams;

using MSHelpLib.BaseCode;

#if !POCKETPC
[assembly: AssemblyTitle("MSHelpLib")]
[assembly: AssemblyDescription("MSHelpLib")]
// [assembly: AssemblyKeyFile(@"..\..\MSHelpLib.snk")]
#endif

namespace MSHelpLib
{
	/// <summary>
	/// Summary description for HelpSystem.
	/// </summary>
	public class HelpSystem : MSHelpLib.IHelpSystem
	{
        /// <summary>
        /// Initializes a new instance of the <see cref="HelpSystem"/> class.
        /// </summary>
		public HelpSystem()
		{
		}

		private ArrayList HelpFiles=new ArrayList();

        /// <summary>
        /// Occurs when [status handler event].
        /// </summary>
		public event MSHelpLib.StatusHandler StatusHandlerEvent;
        /// <summary>
        /// Occurs when [progress handler event].
        /// </summary>
		public event MSHelpLib.ProgressHandler ProgressHandlerEvent;
			
		private int MaxValue=100;
        /// <summary>
        /// Called when [progress].
        /// </summary>
        /// <param name="Value">The value.</param>
        /// <param name="_MaxValue">The _ max value.</param>
		public virtual void OnProgress(int Value, int _MaxValue)
		{			
			MaxValue=_MaxValue;
			if (ProgressHandlerEvent!=null)
				ProgressHandlerEvent(Value,MaxValue);
		}

		private int Level=0;
        /// <summary>
        /// Called when [status].
        /// </summary>
        /// <param name="_Level">The _ level.</param>
        /// <param name="Text">The text.</param>
		public virtual void OnStatus(int _Level, string Text)
		{
			Level=_Level;
			if (StatusHandlerEvent!=null)
				StatusHandlerEvent(Level, Text);
		}

        /// <summary>
        /// Called when [status].
        /// </summary>
        /// <param name="Text">The text.</param>
		public virtual void OnStatus(string Text)
		{
			OnStatus(0,Text);
		}

        /// <summary>
        /// Opens the specified file name.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public IHelpFile Open(string FileName)
		{
			return Open(FileName, (int)(LoadFlags.LoadDumpFile|LoadFlags.SaveDumpFile|LoadFlags.CreateSearchIndexOnFirstUseAndSave));
		}

		private int m_Flags=0;
        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        /// <value>The flags.</value>
		public int Flags
		{
			get
			{
				return m_Flags;
			}

            set
			{
                m_Flags = value;
                foreach (IHelpFile helpFile in HelpFiles)
                {
                    
                }
			}
		}

        /// <summary>
        /// Opens the specified file name.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <param name="flags">The flags.</param>
        /// <returns></returns>
		public IHelpFile Open(string FileName, int flags)
		{
			m_Flags=flags;
			OnStatus("Loading "+FileName);

			FileInfo fi=new FileInfo(FileName);
			foreach(IHelpFile helpFile in HelpFiles)
			{
				if (helpFile.FileName==fi.FullName)	
					return helpFile;
			}

			// Determine which file type file is
			IHelpFile HelpFileToLoad=null;			

			// Check to see if file being opened is .MSH
			if (fi.Extension.ToLower()==".msh")
			{
				// Read Info from MSH File
				FileArchive fa=new FileArchive(Path.ChangeExtension(fi.FullName,".msh"));					
				PartFileStream pfs=fa.ExtractFile("Info");
				BinaryReader br=new BinaryReader(pfs);
				string Version=br.ReadString();
				string HelpName=br.ReadString();

				// Make new filename
				FileInfo fi2=new FileInfo(HelpName);
				string NewFileName=fi.FullName;
				NewFileName=Path.ChangeExtension(NewFileName,fi2.Extension);
				fi=new FileInfo(NewFileName);
			}

			// Open file.
			if ((fi.Extension.ToLower()==".hxs") || (fi.Extension.ToLower()==".hxi"))
			{
				HelpFileToLoad=new MSHelpLib.HXS.Decoding.HXSFile(this,FileName,flags,new MSHelpLib.StatusHandler(OnStatus),new MSHelpLib.ProgressHandler(OnProgress));
			}
			
			if (fi.Extension.ToLower()==".chm") 
			{
				HelpFileToLoad=new MSHelpLib.CHM.Decoding.CHMFile(this,FileName,flags, new MSHelpLib.StatusHandler(OnStatus),new MSHelpLib.ProgressHandler(OnProgress));
			}

			// Check we created an HelpFile object OK
			if (HelpFileToLoad==null)				
				return null;
			
			// Add new IHelpFile object to List of files
			HelpFiles.Add(HelpFileToLoad);		

			if ((Flags & (int)(LoadFlags.LoadOnlySystemData))!=0)
				return HelpFileToLoad;

			// Link TOC
			if (HelpFileToLoad.HasTableOfContents)
			{
				if (TOC==null)
					TOC=new TableOfContents();

				foreach(TOCItem subItem in HelpFileToLoad.TableOfContents.TOC)
				{
					TOC.TOC.Add(subItem);
				}
			}

			// Get Index's
			if (HelpFileToLoad.HasIndex)
			{
				if (_index==null)
					_index=HelpFileToLoad.Index;
			}

			// Load extra files to make TOC complete
			if (HelpFileToLoad.MergeLinks.Count>0)
			{
				foreach(TOCItem item in HelpFileToLoad.MergeLinks)
				{
					if (item.MergeLink!="")
					{
						// FileName::\TOCFileName
						item.MergeLink=item.MergeLink.ToLower();
						if (item.MergeLink.StartsWith("ms-help:"))
							item.MergeLink=item.MergeLink.Substring(8);
						if (item.MergeLink.StartsWith("ms-its:"))
							item.MergeLink=item.MergeLink.Substring(7);						
						string MergeFileName=fi.DirectoryName+@"\"+item.MergeLink.Substring(0,item.MergeLink.IndexOf(@"::"));						
						
						IHelpFile mergeFile=null;
						if ((fi.Extension.ToLower()==".hxs") || (fi.Extension.ToLower()==".hxi"))
							mergeFile=new MSHelpLib.HXS.Decoding.HXSFile(this,MergeFileName,Flags, new MSHelpLib.StatusHandler(OnStatus),new MSHelpLib.ProgressHandler(OnProgress));
						if (fi.Extension.ToLower()==".chm") 
							mergeFile=new MSHelpLib.CHM.Decoding.CHMFile(this,MergeFileName,Flags,new MSHelpLib.StatusHandler(OnStatus),new MSHelpLib.ProgressHandler(OnProgress));

						if (mergeFile==null)
							continue;

						// Merge TOC into this TOC
						if (mergeFile.HasTableOfContents)
						{
							foreach(TOCItem subItem in mergeFile.TableOfContents.TOC)
							{
								item.Children.Add(subItem);
							}
						}

						// Merge Indexs into this Index
						if (mergeFile.HasIndex)
						{
							if (mergeFile.Index.KLinks.Count>0)
								_index.MergeIndex(mergeFile.Index.KLinks,IndexType.KeywordLinks);
								
							if (mergeFile.Index.ALinks.Count>0)
								_index.MergeIndex(mergeFile.Index.ALinks,IndexType.AssociativeLinks);							
						}						
						
						// Add this file to list of opened files	
						HelpFiles.Add(mergeFile);
					}
				}				
			}			

			// All Done 
			return HelpFileToLoad;
		}

        /// <summary>
        /// Closes this instance.
        /// </summary>
		public void Close()
		{			
			foreach(IHelpFile HelpFile in HelpFiles)
			{
				if (HelpFile!=null)
					HelpFile.Close();
			}
			if (HelpFiles!=null)
				HelpFiles.Clear();
		}

        /// <summary>
        /// Readonly property which holds the number of files contained in CHM/HXS files loaded
        /// </summary>
        /// <value></value>
		public int NumberOfFiles
		{
			get
			{
				int FileCount=0;
				foreach(IHelpFile HelpFile in HelpFiles)
				{
					FileCount+=HelpFile.NumberOfFiles;
				}	
				return FileCount;
			}
		}

        /// <summary>
        /// Gets the help file.
        /// </summary>
        /// <param name="i">The i.</param>
        /// <returns></returns>
		public IHelpFile GetHelpFile(int i)
		{
			if (i<0) return null;
			if (i>=HelpFiles.Count)
				return null;
			return (IHelpFile)HelpFiles[i];
		}

        /// <summary>
        /// Readonly property which holds the number of CHM/HXS files loaded
        /// </summary>
        /// <value>The files loaded.</value>
		public int FilesLoaded
		{
			get
			{
				return HelpFiles.Count;
			}
		}

        /// <summary>
        /// Gets a value indicating whether this instance has table of contents.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance has table of contents; otherwise, <c>false</c>.
        /// </value>
		public bool HasTableOfContents
		{
			get
			{
				foreach(IHelpFile helpFile in HelpFiles)
				{
					if (helpFile.HasTableOfContents)
						return true;
				}
				return false;
			}
		}

		private TableOfContents TOC=null;
        /// <summary>
        /// Gets the table of contents.
        /// </summary>
        /// <value>The table of contents.</value>
		public TableOfContents TableOfContents
		{
			get
			{
				if (TOC!=null)
					return TOC;
				
				TOC=new TableOfContents();
				foreach(IHelpFile helpFile in HelpFiles)
				{
					if (helpFile.HasTableOfContents)
						TOC.MergeToC(helpFile.TableOfContents.TOC);						
				}
				return TOC;
			}
		}

        /// <summary>
        /// Gets a value indicating whether this instance has index.
        /// </summary>
        /// <value><c>true</c> if this instance has index; otherwise, <c>false</c>.</value>
		public bool HasIndex
		{
			get
			{
				foreach(IHelpFile helpFile in HelpFiles)
				{
					if (helpFile.HasIndex)
						return true;
				}
				return false;
			}
		}

        /// <summary>
        /// Gets the type of the help index.
        /// </summary>
        /// <value>The type of the help index.</value>
		public IndexType HelpIndexType
		{
			get
			{
				return IndexType.None;
			}
		}

		private Index _index=null;
        /// <summary>
        /// Gets the index.
        /// </summary>
        /// <value>The index.</value>
		public Index Index
		{
			get
			{
				if (_index!=null)
					return _index;
				
				_index=new Index();
				foreach(IHelpFile helpFile in HelpFiles)
				{
					if (helpFile.HasIndex)
					{
						if (helpFile.Index!=null)
						{
							if (helpFile.Index.ALinks.Count>0)
								_index.MergeIndex(helpFile.Index.ALinks,IndexType.KeywordLinks);
						}
					}
				}
				return _index;
			}
		}

        /// <summary>
        /// Gets a value indicating whether this instance can search.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can search; otherwise, <c>false</c>.
        /// </value>
		public bool CanSearch
		{
			get
			{	
				bool bSearch=false;
				foreach(IHelpFile helpFile in HelpFiles)
				{
					if (helpFile.CanSearch)	
						bSearch=true;
				}
				
				return bSearch;
			}
		}

        /// <summary>
        /// Performs the search.
        /// </summary>
        /// <param name="Words">The words.</param>
        /// <returns></returns>
		public SearchResultsTable PerformSearch(string Words)
		{		
			SearchResultsTable Hits=null;
			
			foreach(IHelpFile helpFile in HelpFiles)
			{
				if (helpFile.CanSearch)
				{
					SearchResultsTable _hits=helpFile.PerformSearch(Words);
					if ((_hits==null) || (_hits.Rows.Count==0))
						continue;

					if (Hits==null) 
					{
						Hits=_hits;
						continue;
					}
					
					// append rows from 2nd file
					foreach(SearchResultsRow curRow in _hits.Rows)
					{
						Hits.ImportRow( curRow );
					}

					Hits.DefaultView.Sort = "Rank DESC";
				}
			}
			
			if (Hits==null)
				return null;

			if (Hits.Rows.Count==0)
				return null;
					
			return Hits;
		}

        /// <summary>
        /// Finds the file.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public IFileInfo FindFile(string FileName)
		{
			foreach(IHelpFile HelpFile in HelpFiles)
			{
				IFileInfo fileInfo=HelpFile.FindFile(FileName);
				if (fileInfo!=null)
					return fileInfo;
			}
			return null;
		}

		IFileInfo MSHelpLib.IHelpSystem.FindFile(string FileName, IHelpFile HelpFile)
		{
			return HelpFile.FindFile(FileName);
		}

        /// <summary>
        /// Extracts the specified file info.
        /// </summary>
        /// <param name="fileInfo">The file info.</param>
        /// <returns></returns>
		public System.IO.MemoryStream Extract(IFileInfo fileInfo)
		{
			return fileInfo.HelpStream.Extract(fileInfo);			
		}

        /// <summary>
        /// Extracts as text.
        /// </summary>
        /// <param name="fileInfo">The file info.</param>
        /// <returns></returns>
		public string ExtractAsText(IFileInfo fileInfo)
		{
			if (fileInfo==null)
				throw new ArgumentException("fileInfo is null");
						
			if (fileInfo.Length<=0)
				throw new ArgumentException("fileInfo is pointing to a virtual Help File directory - Can not extract as text string");

			string Text="";			
			try
			{
				MemoryStream st=fileInfo.HelpStream.Extract(fileInfo);
				st.Seek(0,SeekOrigin.Begin);
				
				if (st.Length==0)
					return Text;

				long start = 0;
				int Len=(int)st.Length;

                byte [] buf=new byte[4];
                st.Read(buf ,0,3);                
                if ((buf[0] == 0xef) && (buf[1] == 0xbb) && (buf[2] == 0xbf))
                {
                    start = 3;
					Len-=3;
                }
				
				ASCIIEncoding ascii=new ASCIIEncoding();
				st.Seek(start,SeekOrigin.Begin);
				
                Text = ascii.GetString(st.ToArray(), 0,  Len);

				// UTF Decoding
				st.Seek(start,SeekOrigin.Begin);
				if (Text.ToUpper().IndexOf("UTF-8")!=-1)
				{
					UTF8Encoding utf8 = new UTF8Encoding();
                    Text = utf8.GetString(st.ToArray(), 0, Len);				
				}
				else
                    Text = ascii.GetString(st.ToArray(), 0, Len);
			}
			catch(Exception ex)
			{
				Text="##-"+ex.Message;
			}
			return Text;
		}

        /// <summary>
        /// Processes all files.
        /// </summary>
        /// <param name="processAllFiles">The process all files.</param>
        /// <returns></returns>
		public int ProcessAllFiles(MSHelpLib.ProcessAllFilesDelegate processAllFiles)
		{
			foreach(IHelpFile HelpFile in HelpFiles)
			{
				int rc=HelpFile.ProcessAllFiles(processAllFiles);
				if (rc==0)
					return 0;
			}
			return 1;
		}	
		
		/// <summary>
		/// Gets/Sets the flag specifying if the system should use the tree-images list
		/// from HtmlHelp2. If false the standard CHM-Viewer pics will be used.
		/// </summary>
		private static bool _useHH2TreePics = false;
        /// <summary>
        /// Gets or sets a value indicating whether [use H h2 tree pics].
        /// </summary>
        /// <value><c>true</c> if [use H h2 tree pics]; otherwise, <c>false</c>.</value>
		public static bool UseHH2TreePics
		{
			get { return _useHH2TreePics; }
			set { _useHH2TreePics = value; }
		}

		/// <summary>
		/// Internal member storing the read information types
		/// </summary>
		private ArrayList _informationTypes = new ArrayList();

		/// <summary>
		/// Returns true if the HtmlHelpSystem instance contains 1 or more information types
		/// </summary>
		public bool HasInformationTypes
		{
			get { return (_informationTypes.Count>0); }
		}

		/// <summary>
		/// Gets the information type specified by its name
		/// </summary>
		/// <param name="name">name of the information type to receive</param>
		/// <returns>Returns the Instance for the name or null if not found</returns>
		public InformationType GetInformationType(string name)
		{
			if(HasInformationTypes)
			{
				for(int i=0; i<_informationTypes.Count;i++)
				{
					InformationType iT = _informationTypes[i] as InformationType;

					if(iT.Name == name)
						return iT;
				}
			}

			return null;
		}

        /// <summary>
        /// Inits the search.
        /// </summary>
		public void InitSearch()
		{
			this.OnStatus("Please Wait");
			foreach(IHelpFile helpFile in HelpFiles)
			{
				helpFile.InitSearch();
			}
		}

        /// <summary>
        /// Gets the or load help file.
        /// </summary>
        /// <param name="FileName">Name of the file.</param>
        /// <returns></returns>
		public IHelpFile GetOrLoadHelpFile(string FileName)
		{
			foreach(IHelpFile helpFile in HelpFiles)			
			{
				if (helpFile.FInfo.Name.ToLower()==Path.GetFileName(FileName).ToLower())
				{
					return helpFile;
				}
			}
			return this.Open(FileName,Flags);
		}
	}

    /// <summary>
    /// 
    /// </summary>
	public class SearchComparer : IComparer
	{
		int IComparer.Compare(Object x, Object y)
		{
			IndexItem2 item1 = (IndexItem2)x;
			IndexItem2 item2 = (IndexItem2)y;
			return ((new CaseInsensitiveComparer()).Compare(item1.FullURL, item2.FullURL));
		}
	}
}
